namespace DCHealthCheck.DomainModel {

    #region Usings
    using DCHealthCheck.Common;
    using System;
    using System.Collections.Generic;
    using System.ComponentModel.DataAnnotations;
    using System.ComponentModel.DataAnnotations.Schema;
    using System.Diagnostics;
    using System.IO;
    using System.Linq;
    using System.Text; 
    #endregion

    [Table("ProcessInfo")]
    public class ProcessInfo : IProcessInfo, IObjectWithState, IValidatableObject {

        #region Members
        #region Max length fields
        public static readonly int ElapsedTimeMaxLength = 255;
        public static readonly int CPUTimeMaxLength = 255;
        public static readonly int CommandLineMaxLength = 2048;
        public static readonly int ExecutablePathMaxLength = 2048;
        public static readonly int ExecutableMaxLength = 900;
        #endregion

        public virtual string CommandLine { get; set; }

        [DatabaseGenerated(DatabaseGeneratedOption.Computed)]
        public virtual Nullable<long> CPUTicks { get; set; }

        public virtual string CPUTime {
            get { return this.ProcessorTime.ToString(); }
            set { cpuTime = value; }
        }
        private string cpuTime;

        public static readonly string CSVHeader =
            "DCName,Domain,ADSite,OSVersion,HealthCheckDate," +
            "ProcessId,ParentProcessId,StartDateTime,ElapsedTime,CPUTime,CPUTimePerDay,UserModeTime,KernelModeTime,Priority," +
            "CommandLine,Executable,ExecutablePath,PageFaults,VirtualMemoryBytes,WorkingSetBytes,PrivateBytes," +
            "PagePoolBytes,NonPagePoolBytes,HandleCount,ThreadCount,ReadBytes,WriteBytes,OtherBytes";

        public virtual string ElapsedTime {
            get { return ElapsedTimeSpan.ToString(); }
            set { elapsedTime = value; }
        }
        private string elapsedTime;

        private TimeSpan ElapsedTimeSpan {
            get {
                return TimeSpan.FromTicks(this.ElapsedTimeTicks);
            }
        }

        public virtual long ElapsedTimeTicks { get; set; }

        public virtual string Executable { get; set; }

        public virtual string ExecutablePath { get; set; }

        public virtual int HandleCount { get; set; }

        public virtual long Id { get; set; }

        public virtual long KernelModeTicks { get; set; }

        public virtual long NonPagePoolBytes { get; set; }

        public virtual long OtherBytes { get; set; }

        public virtual long PageFaults { get; set; }

        public virtual long PagePoolBytes { get; set; }

        public virtual long ParentProcessId { get; set; }

        public virtual int Priority { get; set; }

        public virtual long PrivateBytes { get; set; }

        public virtual long ProcessId { get; set; }

        public virtual long ReadBytes { get; set; }

        public virtual DateTime StartDateTime { get; set; }

        public virtual long ThreadCount { get; set; }

        public virtual long UserModeTicks { get; set; }

        public virtual long VirtualMemoryBytes { get; set; }

        public virtual long WriteBytes { get; set; }

        public virtual long WorkingSetBytes { get; set; }

        #region Related Entities
        public virtual SystemInfo SystemInfo {
            get { return systemInfo; }
            set {
                systemInfo = value;
                if (systemInfo != null) {
                    if (this.SystemInfoId != systemInfo.Id) {
                        this.SystemInfoId = systemInfo.Id;
                    }
                }
            }
        }
        private SystemInfo systemInfo;

        [ForeignKey("SystemInfo")]
        public virtual Guid SystemInfoId {
            get {
                if (this.SystemInfo != null) return this.SystemInfo.Id;
                return systemInfoId;
            }
            set { systemInfoId = value; }
        }
        private Guid systemInfoId;
        #endregion

        #region NotMapped properties
        [NotMapped]
        public DetachedState DetachedState { get; set; }

        [NotMapped]
        public Object EntityKey { get { return this.Id; } }

        /// <summary>
        ///  Trend OfficeScan RealTime Monitor Watchdog processes have a random file name and reside in C:\Windows\Temp
        /// </summary>
        [NotMapped]
        public bool IsTrendWatchdog {
            get {
                isTrendWatchDog = false;
                if (this.ExecutablePath.StartsWith(@"C:\Windows\Temp\", StringComparison.CurrentCultureIgnoreCase)) {
                    if (!this.ExecutablePath.Any(x => Path.GetInvalidPathChars().Contains(x))) {
                        isTrendWatchDog = (Path.GetFileNameWithoutExtension(this.ExecutablePath).Length >= 4) &&
                            (Path.GetFileNameWithoutExtension(this.ExecutablePath).Length <= 7);
                    }
                }

                return isTrendWatchDog;
            }
        }
        private bool isTrendWatchDog;

        [NotMapped]
        public TimeSpan ProcessorTime {
            get {
                return TimeSpan.FromTicks(this.KernelModeTicks + this.UserModeTicks);
            }
        }

        [NotMapped]
        public TimeSpan ProcessorTimePerDay {
            get {
                if (this.ElapsedTimeSpan.TotalDays <= 0) {
                    return this.ProcessorTime;
                }
                else {
                    return TimeSpan.FromTicks((long)((this.KernelModeTicks + this.UserModeTicks) / this.ElapsedTimeSpan.TotalDays));
                }
            }
        }

        [NotMapped]
        public Dictionary<string, object> StartingOriginalValues { get; set; }
        #endregion 
        #endregion

        #region Constructor
        public ProcessInfo() {
            this.CommandLine = string.Empty;
            this.ElapsedTimeTicks = -1;
            this.ExecutablePath = string.Empty;
            this.Executable = string.Empty;
            this.HandleCount = -1;
            this.KernelModeTicks = -1;
            this.NonPagePoolBytes = -1;
            this.OtherBytes = -1;
            this.PageFaults = -1;
            this.PagePoolBytes = -1;
            this.ParentProcessId = -1;
            this.Priority = -1;
            this.PrivateBytes = -1;
            this.ProcessId = -1;
            this.ReadBytes = -1;
            this.ThreadCount = -1;
            this.UserModeTicks = -1;
            this.VirtualMemoryBytes = -1;
            this.WorkingSetBytes = -1;
            this.WriteBytes = -1;
        } 
        #endregion

        #region Methods
        public string ToCSVString() {
            var info = new StringBuilder();

            #region DC Info
            if ((this.SystemInfo != null) && (this.SystemInfo.DomainController != null)) {
                info.AppendFormat("\"{0}\",", this.SystemInfo.DomainController.SamAccountNameFriendly);
                info.AppendFormat("\"{0}\",", this.SystemInfo.DomainController.NetbiosDomain);
                info.AppendFormat("\"{0}\",", this.SystemInfo.DomainController.ADSite);
                info.AppendFormat("\"{0}\",", this.SystemInfo.DomainController.OSVersion);
            }
            else {
                info.Append(",,,,");
            }
            #endregion
            #region DateTime Info
            if (this.SystemInfo != null) {
                info.AppendFormat("\"{0}\",", this.SystemInfo.CheckDateTimeUTC.ToString("yyyy-MM-dd HH:mm"));
            }
            else {
                info.Append(",");
            }
            #endregion

            info.AppendFormat("\"{0}\",", this.ProcessId);
            info.AppendFormat("\"{0}\",", this.ParentProcessId);

            info.AppendFormat("\"{0}\",", this.StartDateTime.ToString("yyyy-MM-dd HH:mm"));
            info.AppendFormat("\"{0}\",", TimeSpan.FromTicks(this.ElapsedTimeTicks).DHMSFriendly());
            info.AppendFormat("\"{0}\",", this.ProcessorTime.DHMSFriendly());
            info.AppendFormat("\"{0}\",", this.ProcessorTimePerDay.DHMSFriendly());
            info.AppendFormat("\"{0}\",", TimeSpan.FromTicks(this.UserModeTicks).DHMSFriendly());
            info.AppendFormat("\"{0}\",", TimeSpan.FromTicks(this.KernelModeTicks).DHMSFriendly());
            info.AppendFormat("\"{0}\",", this.Priority);

            info.AppendFormat("\"{0}\",", this.CommandLine);
            info.AppendFormat("\"{0}\",", this.Executable);
            info.AppendFormat("\"{0}\",", this.ExecutablePath);

            info.AppendFormat("\"{0}\",", this.PageFaults);
            info.AppendFormat("\"{0}\",", this.VirtualMemoryBytes);
            info.AppendFormat("\"{0}\",", this.WorkingSetBytes);
            info.AppendFormat("\"{0}\",", this.PrivateBytes);
            info.AppendFormat("\"{0}\",", this.PagePoolBytes);
            info.AppendFormat("\"{0}\",", this.NonPagePoolBytes);
            info.AppendFormat("\"{0}\",", this.HandleCount);
            info.AppendFormat("\"{0}\",", this.ThreadCount);
            info.AppendFormat("\"{0}\",", this.ReadBytes);
            info.AppendFormat("\"{0}\",", this.WriteBytes);
            info.AppendFormat("\"{0}\",", this.OtherBytes);


            return info.ToString();
        }

        [DebuggerStepThroughAttribute]
        public override string ToString() {
            var info = new StringBuilder();

            info.Append("ProcessInfo: ");

            if ((this.SystemInfo != null) && (this.SystemInfo.DomainController != null) && (this.SystemInfo.DomainController.DNSName != null)) {
                info.AppendFormat("DC: {0}; ", this.SystemInfo.DomainController.DNSName);
            }
            if (this.SystemInfo != null) {
                info.AppendFormat("HealthCheck Date: {0}; ", this.SystemInfo.CheckDateTimeUTC.ToString("yyyy-MMM-dd"));
            }
            info.AppendFormat("Process Id: {0}; ", (this.ProcessId));
            if (!string.IsNullOrWhiteSpace(this.ExecutablePath)) {
                info.AppendFormat("ExecutablePath: {0}", this.ExecutablePath);
            }
            else {
                info.AppendFormat("CommandLine: {0}", this.CommandLine);
            }

            return info.ToString();
        }

        public IEnumerable<ValidationResult> Validate(ValidationContext validationContext) {
            if ((this.DetachedState != DetachedState.Added) && (this.Id < 1)) {
                yield return new ValidationResult(
                    "Id is required.", new[] { "Id" });
            }

            if (this.DetachedState != DetachedState.Deleted) {
                // add entity to SystemInfo, let DetectChanges perform relationship fixup
                if (this.SystemInfoId == Guid.Empty) {
                    yield return new ValidationResult(
                        "SystemInfoId is required.", new[] { "SystemInfoId" });
                }

                if ((this.SystemInfo != null) && (this.SystemInfo.Id != this.SystemInfoId)) {
                    yield return new ValidationResult(
                        "SystemInfo.Id does not match SystemInfoId.", new[] { "SystemInfo, SystemInfoId" });
                }

                if (this.ProcessId < 0) {
                    yield return new ValidationResult(
                        "ProcessId is required.", new[] { "ProcessId" });
                }

                if (this.ElapsedTime.Length > ElapsedTimeMaxLength) {
                    yield return new ValidationResult(string.Format("ElapsedTime length: {0} exceeds max length: {1}",
                        this.ElapsedTime.Length, ElapsedTimeMaxLength), new[] { "ElapsedTime" });
                }

                if (this.CPUTime.Length > CPUTimeMaxLength) {
                    yield return new ValidationResult(string.Format("CPUTime length: {0} exceeds max length: {1}",
                        this.CPUTime.Length, CPUTimeMaxLength), new[] { "CPUTime" });
                }

                if (this.CommandLine == null) {
                    yield return new ValidationResult(
                        "CommandLine is required.", new[] { "CommandLine" });
                }
                else if (this.CommandLine.Length > CommandLineMaxLength) {
                    yield return new ValidationResult(string.Format("CommandLine length: {0} exceeds max length: {1}",
                        this.CommandLine.Length, CommandLineMaxLength), new[] { "CommandLine" });
                }

                if (this.ExecutablePath == null) {
                    yield return new ValidationResult(
                        "ExecutablePath is required.", new[] { "ExecutablePath" });
                }
                else if (this.ExecutablePath.Length > ExecutablePathMaxLength) {
                    yield return new ValidationResult(string.Format("ExecutablePath length: {0} exceeds max length: {1}",
                        this.ExecutablePath.Length, ExecutablePathMaxLength), new[] { "ExecutablePath" });
                }
                else if (this.ExecutablePath.Contains("\"")) {
                    yield return new ValidationResult(
                        "ExecutablePath cannot contain quotes.", new[] { "ExecutablePath" });
                }

                if (this.Executable == null) {
                    yield return new ValidationResult("Executable is required.", new[] { "Executable" });
                }
                else if (this.Executable.Length > ExecutableMaxLength) {
                    yield return new ValidationResult(string.Format("Executable length: {0} exceeds max length: {1}",
                        this.Executable.Length, ExecutableMaxLength), new[] { "Executable" });
                }

                if ((this.ProcessId > 4) && (this.StartDateTime == DateTime.MinValue)) {
                    yield return new ValidationResult(
                        string.Format("StartDateTime invalid: {0}.  Must be greater than: {1}",
                        this.StartDateTime.ToString("yyyy-mm-dd HH:mm"), DateTime.MinValue.ToString("yyyy-mm-dd HH:mm")),
                        new[] { "StartDateTime" });
                }
            } // if (this.DetachedState != State.Deleted)
        }  
        #endregion
        
    }
}
